package it.uniroma2.art.owlart.owlimimpl.factory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import org.openrdf.model.URI;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.manager.RemoteRepositoryManager;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.sail.Sail;
import org.openrdf.sail.SailException;
import org.openrdf.sail.config.SailConfigException;
import org.openrdf.sail.helpers.NotifyingSailBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ontotext.trree.owlim_ext.config.OWLIMSailConfig;
import com.ontotext.trree.owlim_ext.config.OWLIMSailFactory;
import com.ontotext.trree.owlim_ext.config.OWLIMSailSchema;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelCreationException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.exceptions.UnsupportedRDFFormatException;
import it.uniroma2.art.owlart.exceptions.VocabularyInitializationException;
import it.uniroma2.art.owlart.io.RDFFormat;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.models.BaseRDFTripleModel;
import it.uniroma2.art.owlart.models.ModelFactory;
import it.uniroma2.art.owlart.models.OWLArtModelFactory;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.models.RDFModel;
import it.uniroma2.art.owlart.models.RDFSModel;
import it.uniroma2.art.owlart.models.SKOSModel;
import it.uniroma2.art.owlart.models.SKOSXLModel;
import it.uniroma2.art.owlart.models.TripleQueryModelHTTPConnection;
import it.uniroma2.art.owlart.models.UnloadableModelConfigurationException;
import it.uniroma2.art.owlart.models.UnsupportedModelConfigurationException;
import it.uniroma2.art.owlart.owlimimpl.models.BaseRDFModelOWLIMImpl;
import it.uniroma2.art.owlart.owlimimpl.models.conf.OWLIMLocalModelConfiguration;
import it.uniroma2.art.owlart.owlimimpl.models.conf.OWLIMModelConfiguration;
import it.uniroma2.art.owlart.owlimimpl.models.conf.OWLIMRemoteModelConfiguration;
import it.uniroma2.art.owlart.owlimimpl.reasoning.InvalidRuleSetSpecificationException;
import it.uniroma2.art.owlart.owlimimpl.reasoning.RuleSet;
import it.uniroma2.art.owlart.resources.Resources;
import it.uniroma2.art.owlart.sesame2impl.models.BaseRDFModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.OWLModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.RDFModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.RDFSModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.SKOSModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.SKOSXLModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.TripleQueryModelHTTPConnectionSesame2Impl;
import it.uniroma2.art.owlart.vocabulary.OWL;
import it.uniroma2.art.owlart.vocabulary.SKOS;
import it.uniroma2.art.owlart.vocabulary.SKOSXL;

/**
 * This class implements {@link ModelFactory}<br/>
 * 
 * A fast and easy way to load an RDF/RDFS/OWL Model by using this OWLIM implementation is to wrap it through
 * the OWL API convenience class {@link OWLArtModelFactory}, which handles standard configuration of models of
 * the RDF family, like loading proper vocabularies, setting baseuri/defnamespace of the loaded model etc... <br/>
 * This is the code to do that:
 * 
 * <pre>
 * ModelFactory fact = OWLArtModelFactory.createModelFactory(&lt;an instance of this class&gt;);
 * </pre>
 * 
 * @author Manuel Fiorelli <manuel.fiorelli@gmail.com>
 */
public class ARTModelFactoryOWLIMImpl implements ModelFactory<OWLIMModelConfiguration> {

	protected static Logger logger = LoggerFactory.getLogger(ARTModelFactoryOWLIMImpl.class);

	private ArrayList<Class<? extends OWLIMModelConfiguration>> supportedConfigurationClasses;

	protected boolean populatingW3CVocabularies = true;

	public ARTModelFactoryOWLIMImpl() {
		supportedConfigurationClasses = new ArrayList<Class<? extends OWLIMModelConfiguration>>();
		supportedConfigurationClasses.add(OWLIMLocalModelConfiguration.class);
		supportedConfigurationClasses.add(OWLIMRemoteModelConfiguration.class);
	}

	/**
	 * Converts the configuration parameters. It translates the data from the OWLArt model to the OWLIM
	 * specific model.
	 * 
	 * @param sourceConfig
	 *            an OWLArt Model configuration
	 * @param targetConfig
	 *            an OWLIM Sail configuration
	 */
	private <MCImpl extends OWLIMLocalModelConfiguration> void owlartconfig2owlimsailconfig(
			MCImpl sourceConfig, OWLIMSailConfig targetConfig, String baseuri, String persistenceDirectory) {
		HashMap<URI, String> params = new HashMap<URI, String>();

		params.put(OWLIMSailSchema.noPersist,
				((OWLIMLocalModelConfiguration) sourceConfig).isPersistent() ? "false" : "true");
		params.put(OWLIMSailSchema.repository_type,
				((OWLIMLocalModelConfiguration) sourceConfig).repositoryType);
		params.put(OWLIMSailSchema.ruleset, ((OWLIMLocalModelConfiguration) sourceConfig).ruleSet);

		params.put(OWLIMSailSchema.baseURL, baseuri);
		params.put(OWLIMSailSchema.storagefolder, persistenceDirectory);

		targetConfig.setConfigParams(params);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#createModelConfigurationObject(java.lang.Class)
	 */
	public <MCImpl extends OWLIMModelConfiguration> MCImpl createModelConfigurationObject(
			Class<MCImpl> mcclass) throws UnsupportedModelConfigurationException,
			UnloadableModelConfigurationException {
		logger.debug("creating ModelConfigurationObject");
		if (supportedConfigurationClasses.contains(mcclass)) {
			// if (true) {
			try {

				// logger.debug("class loader of: " + this.getClass().getSimpleName() + " instance: " +
				// this.getClass().getClassLoader());
				// logger.debug("class loader of static: " + ARTModelFactorySesame2Impl.class.getSimpleName()
				// + " : " + ARTModelFactorySesame2Impl.class.getClassLoader());
				// logger.debug("class loader of passed model configuration class: " + mcclass.getSimpleName()
				// + "inside: " + this.getClass().getSimpleName() + " = " + mcclass.getClassLoader());
				// logger.debug("class loader of inner model configuration class: " +
				// innerMConfCls.getSimpleName() + "inside: " + this.getClass().getSimpleName() + " = " +
				// innerMConfCls.getClassLoader());

				logger.debug("requested model configuration class: " + mcclass);
				// logger.debug("inner model configuration class: " + innerMConfCls );

				MCImpl mConf = (MCImpl) mcclass.newInstance();

				// MCImpl mConf = mcclass.newInstance();

				// logger.debug("mConf: " + mConf );
				// return mcclass.newInstance();
				return mConf;
			} catch (InstantiationException e) {
				throw new UnloadableModelConfigurationException(mcclass);
			} catch (IllegalAccessException e) {
				throw new UnloadableModelConfigurationException(mcclass);
			}
		} else
			throw new UnsupportedModelConfigurationException(this, mcclass);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#getModelConfigurations()
	 */
	public Collection<Class<? extends OWLIMModelConfiguration>> getModelConfigurations() {
		return supportedConfigurationClasses;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFBaseModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> BaseRDFModelOWLIMImpl loadRDFBaseModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {

		// TODO make a uniform use of the modern Repository, instead of the sail interface (waiting for the
		// OWLIM code sample for loading the configuration)
		if (conf instanceof OWLIMLocalModelConfiguration) {

			OWLIMSailFactory factory = new OWLIMSailFactory();
			OWLIMSailConfig owlimSailConfig = (OWLIMSailConfig) factory.getConfig();

			owlartconfig2owlimsailconfig((OWLIMLocalModelConfiguration) conf, owlimSailConfig, baseuri,
					persistenceDirectory);

			try {
				Sail sail = factory.getSail(owlimSailConfig);
				SailRepository sailRepository = new SailRepository(sail);
				sailRepository.initialize();

				RuleSet reas = RuleSet
						.getValue(((OWLIMLocalModelConfiguration) conf).ruleSet);

				return new BaseRDFModelOWLIMImpl(sailRepository, reas);

			} catch (Exception e) {
				throw new ModelCreationException(e.getMessage());
			}
		} else {

			// remote access

			// remote access to an OWLIM repository is based on a pure Sesame2 remote connection. The only
			// reason for not using directly an OWLART-Sesame2 wrapper is that a OWLIM repository has some
			// behavioral differences with respect to Sesame2, which need to be taken into account by the
			// client interface of OWLART

			// TODO these three lines should be put at the start, before the if-then-else, once even the OWLIM
			// part uses the Repository interface (see above)
			// NotifyingSailBase baseSail;
			Repository myRepository;

			OWLIMRemoteModelConfiguration remModelConf = (OWLIMRemoteModelConfiguration) conf;

			try {
				RemoteRepositoryManager repositoryManager = RemoteRepositoryManager.getInstance(
						remModelConf.serverURL, remModelConf.username, remModelConf.password);
				myRepository = repositoryManager.getRepository(remModelConf.repositoryId);
				if (myRepository == null)
					throw new ModelCreationException("there is no repository with id: "
							+ remModelConf.repositoryId);

				logger.info("Sesame2 Remote Repository initialization...");
				myRepository.initialize();

				logger.info("Initializing OWLART OWLIM/Sesame2 Remote Model initialization...");
				RuleSet reas = RuleSet
						.getValue(((OWLIMRemoteModelConfiguration) conf).ruleSet);

				BaseRDFModelOWLIMImpl rep = new BaseRDFModelOWLIMImpl(myRepository, reas);

				return rep;

			} catch (Exception e) {
				throw new ModelCreationException("Problem with remote connection: " + e.getMessage());
			}
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> RDFModel loadRDFModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFModelOWLIMImpl baseRep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);

		try {
			return new RDFModelSesame2Impl(baseRep);
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFSModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> RDFSModel loadRDFSModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFModelOWLIMImpl baseRep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);

		try {
			return new RDFSModelSesame2Impl(baseRep);
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadOWLModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> OWLModel loadOWLModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFModelOWLIMImpl baseRep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);

		try {
			OWLModel rep = new OWLModelSesame2Impl(baseRep);

			ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);
			logger.debug("owl namespace: " + owl.getURI());

			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadSKOSModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> SKOSModel loadSKOSModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFModelOWLIMImpl baseRep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);

		try {
			SKOSModelSesame2Impl rep = new SKOSModelSesame2Impl(baseRep);
			ARTURIResource skos = rep.createURIResource(SKOS.NAMESPACE);
			ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);

			logger.debug("owl namespace: " + owl.getURI());
			logger.debug("skos namespace: " + skos.getURI());

			rep.addRDF(Resources.class.getResource("owl.rdfs"), OWL.NAMESPACE, RDFFormat.RDFXML, owl);
			rep.addRDF(Resources.class.getResource("skos.rdf"), SKOS.NAMESPACE, RDFFormat.RDFXML, skos);

			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (FileNotFoundException e) {
			throw new ModelCreationException(e);
		} catch (IOException e) {
			throw new ModelCreationException(e);
		} catch (ModelAccessException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		} catch (UnsupportedRDFFormatException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadSKOSXLModel(java.lang.String, java.lang.String,
	 * MCImpl)
	 */
	public <MCImpl extends OWLIMModelConfiguration> SKOSXLModel loadSKOSXLModel(String baseuri,
			String persistenceDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFModelOWLIMImpl baseRep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);

		try {

			SKOSXLModelSesame2Impl rep = new SKOSXLModelSesame2Impl(baseRep);

			ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);
			ARTURIResource skos = rep.createURIResource(SKOS.NAMESPACE);
			ARTURIResource skosxl = rep.createURIResource(SKOSXL.NAMESPACE);

			logger.debug("owl namespace: " + owl.getURI());
			logger.debug("skos namespace: " + skos.getURI());
			logger.debug("skos xl namespace: " + skosxl.getURI());

			rep.addRDF(Resources.class.getResource("owl.rdfs"), OWL.NAMESPACE, RDFFormat.RDFXML, owl);
			rep.addRDF(Resources.class.getResource("skos.rdf"), SKOS.NAMESPACE, RDFFormat.RDFXML, skos);
			rep.addRDF(Resources.class.getResource("skos-xl.rdf"), SKOSXL.NAMESPACE, RDFFormat.RDFXML, skosxl);

			return rep;

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (FileNotFoundException e) {
			throw new ModelCreationException(e);
		} catch (IOException e) {
			throw new ModelCreationException(e);
		} catch (ModelAccessException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		} catch (UnsupportedRDFFormatException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniroma2.art.owlart.models.ModelFactory#closeModel(it.uniroma2.art.owlart.models.BaseRDFTripleModel)
	 */
	public void closeModel(BaseRDFTripleModel rep) throws ModelUpdateException {
		rep.close();
	}

	public TripleQueryModelHTTPConnection loadTripleQueryHTTPConnection(String endpointURL)
			throws ModelCreationException {
		return new TripleQueryModelHTTPConnectionSesame2Impl(endpointURL);
	}

	public void setPopulatingW3CVocabularies(boolean pref) {
		populatingW3CVocabularies = pref;
	}

	public boolean isPopulatingW3CVocabularies() {
		return populatingW3CVocabularies;
	}

}
